Zach的博客

Rust FFI

FFI

FFI(Foreign Function Interface),顾名思义,是Rust中用来调用其他语言的机制。值得注意的是,FFI在C和Rust之间实现了零抽象开销,也就是说在Rust调用C和C调用Rust时并没有运行时的花销。

一个例子

比方说我们定义了一个函数:

1
2
3
int double_input(int input) {
return intput * 2;
}

如果我们需要在Rust中调用它,那么main.rs文件应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
extern crate libc;

#[link(name = "extlib")]
extern {
fn double_input(input: libc::c_int) -> libc::c_int;
}

fn main() {
let input = 4;
let output = unsafe { double_input(input) };
println!("{} * 2 = {}", input, output);
}

我们来一步一步剖析这个程序:

  • libc这个库提供了许多类型定义,用以桥街Rust和C的类型。

  • #[link(name = "extlib")]这个Attribute告诉编译器这个函数可以连接libextlib这个库而得到,这里的链接是动态链接。注意link属性还有另外一种形式:#[link(name = "extlib", kind = "static")]kind目前只有两个值:

    • static
    • framework(只在OSX中)

    如果不指定kind,那么默认为动态链接。

  • extern代码块声明从C语言调用的函数接口。

  • 因为编译器不知道C函数是如何实现,所以它假设内存安全问题会在你调用C函数时发生,所以我们必须用unsafe来包含调用C函数的语句。

  • 需要注意的是,我们需要在项目中添加一个build.rs文件来告诉编译如何编译C文件并生成libextlib,具体可以参考这个例子rust-to-c

安全抽象

如果我们把一个C函数绑定到Rust,不仅绑定的抽象开销为零,我们也可以让C函数变得更加安全。我们拿下面的例子来说明:

1
2
3
4
// Gets the data for a file in the tarball at the given index, returning NULL if
// it does not exist. The `size` pointer is filled in with the size of the file
// if successful.
const char *tarball_file_data(tarball_t *tarball, unsigned index, size_t *size);

假设我们调用这个函数,那么函数返回一个指针,而在之后某一个时刻,我们销毁了tarball所在内存,那么这时候这个指针就变成了野指针(dangling pointer),后续如果使用了这个指针,很可能程序就会崩溃。

如果我们把这个函数绑定到Rust:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub struct Tarball { raw: *mut tarball_t }

impl Tarball {
pub fn file(&self, index: u32) -> Option<&[u8]> {
unsafe {
let mut size = 0;
let data = tarball_file_data(self.raw, index as libc::c_uint,
&mut size);
if data.is_null() {
None
} else {
Some(slice::from_raw_parts(data as *const u8, size as usize))
}
}
}
}

data的声明周期默认和一个Tarball对象绑定在一起,如果Tarball已经被销毁,那么data由于Rust的限制也不能被使用,程序编译都不会通过,从而保证了安全。

FFI和panics

需要注意的一点是,如果其他语言函数和panic!在同一个线程中被调用,那么结果是未定义的。如果编写的程序可能panic,我们就得让panic在另一个线程中被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::thread;

#[no_mangle]
pub extern fn oh_no() -> i32 {
let h = thread::spawn(|| {
panic!("Oops!");
});

match h.join() {
Ok(_) => 1,
Err(_) => 0,
}
}